﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using CccNh.Sorting;
using urn.nhibernate.mapping.Item2.Item2;

namespace CccNh.Conventions
{
    public class CreateManyToOneProperties : IClassConvention, IRunAfter<CreateHiloIdIfTypeHasIntIdField>, IRunAfter<CreateHiloIdIfTypeHasIntIdProperty>
    {
        private BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

        public void Apply(Type type, @class @class, IEnumerable<Type> entityTypes, hibernatemapping mapping)
        {
            var entityPropertiesOnType = type.GetProperties(bindingFlags).Where(p => entityTypes.Contains(p.PropertyType)).ToArray();

            foreach (var propertyInfo in entityPropertiesOnType)
            {
                var columnName = GetColumnName(propertyInfo);
                @class.manytoone.Add(new manytoone()
                {
                    name = propertyInfo.Name,
                    column1 = columnName
                });


                //if there is a manytoone, there is probably a set on the other object...
                var potentialReciprocalProperties = propertyInfo.PropertyType.GetProperties(bindingFlags) //... so we get the collections on the other type
                    .Where(p => typeof (IEnumerable<>).MakeGenericType(type).IsAssignableFrom(p.PropertyType));
                
                
                if (potentialReciprocalProperties.Count() > 1)
                {
                    throw new Exception("Meow! There is more than 1 collection that might be the inverse!");
                }
                if (potentialReciprocalProperties.Count() == 1)
                {
                    var otherProp = potentialReciprocalProperties.Single();
                    var otherClassMap = mapping.@class.Single(c => c.name == propertyInfo.PropertyType.AssemblyQualifiedName);
                    if (typeof(Iesi.Collections.Generic.ISet<>).MakeGenericType(type).IsAssignableFrom(otherProp.PropertyType))
                    {
                        otherClassMap.set.Add(new set()
                        {
                            name = otherProp.Name,
                            key = new key(){ column1 = columnName, foreignkey="FK_" + propertyInfo.Name + "_to_" + otherProp.Name},
                            onetomany = new onetomany(){@class = type.AssemblyQualifiedName},
                            cascade = "all"
                        });
                    }
                }
            }
        }

        private static string GetColumnName(PropertyInfo propertyInfo)
        {
            var propName = propertyInfo.PropertyType.Name; //suppose we have a property of type User...
            var columnName = propName + "Id";  //...we write it to a column UserId...
            if (propertyInfo.Name != propName) //...but if the property is called something like CreatorUser...
            {
                columnName = propertyInfo.Name.Replace(propName, "") + "_" + columnName; //...we end up with a column called Creator_UserId
            }
            return columnName;
        }
    }
}